package com.cee.config;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.cee.auditlog.Auditable;
import com.cee.auditlog.bean.NoAuditLog;
import com.cee.auditlog.handler.*;
import com.cee.enums.LastRecTxnType;
import com.cee.util.AuthContextHolder;
import com.cee.util.BeanUtil;
import com.cee.util.SpringContextUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;

import javax.sql.DataSource;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.util.Date;
import java.util.Map;
import java.util.Properties;
@Slf4j
@Intercepts(@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}))
public class MyBatisInterceptor implements Interceptor {


    private boolean auditLogRequired = false;

    public MyBatisInterceptor(boolean auditLogRequired) {
        this.auditLogRequired = auditLogRequired;
    }

    @Override
    public Object intercept(Invocation invocation)
            throws Throwable {

        return handleAuditable(invocation);
    }

    /**
     * 在插入或更新操作時，對Auditable類型的相關字段作出統一的處理
     *
     * @param invocation
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    private Object handleAuditable(Invocation invocation)
            throws Exception {

        SQLHandler sqlHandler = null;

        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];

        // 获取 SQL 命令
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();

        log.debug("sqlCommandType: {}", sqlCommandType);

        // 獲取要操作的Object
        Object object = null;

        TransactionHandler txnHandler = TransactionHandlerContext.getHandler();
        Object mybatisPlusObject = null;
        // 如果是插入或更新操作，並且object是Auditable類型對需要執行以下邏輯
        if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {

            // 插入時， 如果包含lastRecTxnTypeCode字段，設置"I"值到該字段

            if (SqlCommandType.INSERT.equals(sqlCommandType)) {
                if (invocation.getArgs().length > 1) {
                    object = invocation.getArgs()[1];
                }
                Auditable auditable = null;

                if (Auditable.class.isAssignableFrom(object.getClass())) {
                    auditable = ((Auditable) object);
                    auditable.setLastRecTxnTypeCode(LastRecTxnType.I.toString());
                    auditable.setLastRecTxnDate(new Date());
                    if(getUserId() != null){
                        auditable.setLastRecTxnUserId(getUserId());
                    }
                    //auditable.setLastRecTxnUserId(auditable.getLastRecTxnUserId() != null ? auditable.getLastRecTxnUserId() : getUser().getUserId());
                }

                // pre-handle insert type audit trail, the object of AuditLogFacade is no need to audit
                if (auditLogRequired && txnHandler != null && !(object instanceof NoAuditLog)) {
                    try {
                        Executor executor = (Executor) invocation.getTarget();
                        Connection connection = executor.getTransaction().getConnection();
                        sqlHandler = new SQLServerInsertHandler();
                        sqlHandler.preHandle(mappedStatement, object);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }

                // 更新時
            } else {

                Auditable auditable = null;
                if (invocation.getArgs()[1] instanceof Map) {
                    MapperMethod.ParamMap paramMap = ((MapperMethod.ParamMap) invocation.getArgs()[1]);
                    if (paramMap.containsKey(Constants.ENTITY)) {
                        mybatisPlusObject = paramMap.get(Constants.ENTITY);
                    }
                }

                if (mybatisPlusObject == null) {
                    throw new IllegalStateException(
                            "The update method in Mybatis mapper must be using annotation @Param(Constants.ENTITY)");
                }

                if (Auditable.class.isAssignableFrom(mybatisPlusObject.getClass())) {
                    auditable = ((Auditable) mybatisPlusObject);
                    auditable.setLastRecTxnDate(new Date());
                    if(getUserId() != null){
                        auditable.setLastRecTxnUserId(getUserId());
                    }

                    if (auditable.getLastRecTxnTypeCode() != null) {
                        if (LastRecTxnType.U.toString().equals(auditable.getLastRecTxnTypeCode())) {

                            // 如果是邏輯刪除"D"，則需要設置lastRecDelDate為當前時間
                        } else if (LastRecTxnType.D.toString().equals(auditable.getLastRecTxnTypeCode())) {
                            auditable.setLastRecDelDate(new Date());
                        }
                    }
                }

                object = invocation.getArgs()[1];
                // pre-handle insert type audit trail
                log.info("NoAuditLog: {}, {}, {}", object, mybatisPlusObject, (!(mybatisPlusObject instanceof NoAuditLog) && !(object instanceof NoAuditLog)));
                if (auditLogRequired && txnHandler != null && !(mybatisPlusObject instanceof NoAuditLog) && !(object instanceof NoAuditLog)) {

                    try {
                        sqlHandler = new SQLServerUpdateHandler();
                        DataSource dataSource = SpringContextUtil.getBean(DataSource.class);
                        sqlHandler.preHandle(mappedStatement, object);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }

            Object proceed = invocation.proceed();

            if (auditLogRequired && txnHandler != null && !(mybatisPlusObject instanceof NoAuditLog) && !(object instanceof NoAuditLog)) {
                // post-handle audit trail
                if (sqlHandler != null) {
                    String rowId = BeanUtil.getPropValueByAnnotation(object, TableId.class);
                    sqlHandler.setRowId(rowId);

                    txnHandler.addSqlHandler(sqlHandler);
                }
            }

            return proceed;
        } else {
            return invocation.proceed();
        }
    }

    private String getUserId() {
        return AuthContextHolder.getUserId() == null? "testUserId" : AuthContextHolder.getUserId().toString();
    }


    @Override
    public Object plugin(Object target) {
        if (target instanceof Executor) {
            return Plugin.wrap(target, this);
        }
        return target;
    }

    @Override
    public void setProperties(Properties properties) {

    }
}
